///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include <Shader/CgProgram.h>
#include <File/File.h>

//-----------------------------------------------------------------------------

CgProgram::CgProgram()
	:	GPUProgram()
	,	m_cgContext(0)
	,	m_cgVertexShader(0)
	,	m_cgFragmentShader(0)
	,	m_cgVertexProfile(CG_PROFILE_UNKNOWN)
	,	m_cgFragmentProfile(CG_PROFILE_UNKNOWN)
	,	m_cgCombinedShader(0)
{
}

//-----------------------------------------------------------------------------

CgProgram::CgProgram(File* sourceVS, File* sourceFS, const char *vertexFunc, const char *fragFunc)
{
	CgProgram();
	(void)this->Load(sourceVS, sourceFS, vertexFunc, fragFunc);
}

//-----------------------------------------------------------------------------

bool CgProgram::Load(File* sourceVS, File* sourceFS, const char *vertexFunc, const char *fragFunc, const char *argsV, const char *argsF)
{
	// Am incarcat deja un program
	if (m_cgVertexShader || m_cgFragmentShader)
	{
		return false;
	}

	// Nu am primit nici un fisier valid
	if ((!sourceVS || !sourceVS->IsOpen()) && (!sourceFS || !sourceFS->IsOpen()))
	{
		return false;
	}

	if( !vertexFunc || !fragFunc )
	{
		return false;
	}

	// Cream contextul Cg
	m_cgContext = cgCreateContext();
	if (m_cgContext == NULL)
	{
		return false; // A esuat crearea contextului
	}
	
	// Nu vrem sa se autocompileze programele
	cgSetAutoCompile(m_cgContext, CG_COMPILE_MANUAL);

	// Citim continutul programul vertex din fisier si cream vertex shader-ul
	if (sourceVS && sourceVS->IsOpen())
	{
		// Cream intai un vertex profile
		m_cgVertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
		if (m_cgVertexProfile == CG_PROFILE_UNKNOWN)
		{
			// Nu exista un profil valid
			return false;
		}

		sourceVS->Seek(0);
		int size = sourceVS->GetSize();
		char* sourceBuffer = MGL_NEW char[size + 1];
		sourceVS->Read(sourceBuffer, 1, size);
		sourceBuffer[size] = 0;	// Adaugam terminatorul de sir
							
		char **args = NULL;
		//u16 asize = strlen( argsV );
		//if( asize )
		//{
		//	args = MGL_NEW char*[2];
		//	args[0] = (char*)argsV;
		//	args[1] = NULL;
		//}
		// Cream programul Cg
		m_cgVertexShader = 
			cgCreateProgram(m_cgContext, CG_SOURCE, sourceBuffer, m_cgVertexProfile, vertexFunc, (const char**)&args);

		// Eliberam resursele
		SAFE_DEL_ARRAY( sourceBuffer );

		if (m_cgVertexShader == NULL)
		{
			// A esuat crearea programului
			CGerror error = cgGetError();
			
			// Logam eroarea si iesim
			DEBUG_ERR("Cg vertex shader creation failed: %s\n", cgGetErrorString(error));

			const char *strError = cgGetLastListing(m_cgContext);
			if( strError )
			{
				DEBUG_ERR("%s\n", strError );
			}

			return false;
		}
	}

	// Citim continutul programul vertex din fisier si cream fragment shader-ul
	if (sourceFS && sourceFS->IsOpen())
	{
		// Cream intai un fragment profile
		m_cgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
		if (m_cgFragmentProfile == CG_PROFILE_UNKNOWN)
		{
			// Nu exista un profil valid
			return false;
		}

		sourceFS->Seek(0);
		int size = sourceFS->GetSize();
		char* sourceBuffer = MGL_NEW char[size + 1];
		sourceFS->Read(sourceBuffer, 1, size);
		sourceBuffer[size] = 0;	// Adaugam terminatorul de sir

		//char *args = NULL;
		//u16 asize = strlen( argsF );
		//if( asize )
		//{
		//	args = MGL_NEW char[asize+2];
		//	memcpy( args, argsF, asize );
		//	args[asize] = 0;
		//	args[asize+1] = 0;
		//}
		// Cream programul Cg
		m_cgFragmentShader = 
			cgCreateProgram(m_cgContext, CG_SOURCE, sourceBuffer, m_cgFragmentProfile, fragFunc, NULL/*(const char**)&args*/);
		
		// Eliberam resursele
		SAFE_DEL_ARRAY( sourceBuffer );

		if (m_cgFragmentShader == NULL)
		{
			// A esuat crearea programului
			CGerror error = cgGetError();
			
			// Logam eroarea si iesim
			DEBUG_ERR("Cg fragment shader creation failed: %s\n", cgGetErrorString(error));
			
			const char *strError = cgGetLastListing(m_cgContext);
			if( strError )
			{
				DEBUG_ERR("%s\n", strError );
			}
			
			return false;
		}
	}

	return true; // succes
}

//-----------------------------------------------------------------------------

bool CgProgram::Compile()
{
	// Compilam intai vertex shader-ul daca exista
	if (m_cgVertexShader && !cgIsProgramCompiled(m_cgVertexShader))
	{
		// Optiuni compilator pe baza profilului
		cgGLSetOptimalOptions(m_cgVertexProfile);
		
		// COmpilam shader-ul
		cgCompileProgram(m_cgVertexShader);
		
		// Verificam compilarea
		CGerror error = cgGetError();
		if (error != CG_NO_ERROR)
		{
			// Logam eroarea si iesim
			DEBUG_ERR("Cg vertex shader compilation failed: %s\n", cgGetErrorString(error));

			const char *strError = cgGetLastListing(m_cgContext);
			if( strError )
			{
				DEBUG_ERR("%s\n", strError );
			}

			return false;
		}
		else
		{
			DEBUG_OUT("Cg vertex shader compilation successful\n");
		}
	}
	
	// Compilam fragment shader-ul
	if (m_cgFragmentShader && !cgIsProgramCompiled(m_cgFragmentShader))
	{
		// Optiuni compilator pe baza profilului
		cgGLSetOptimalOptions(m_cgFragmentProfile);
		
		// Compilam shader-ul
		cgCompileProgram(m_cgFragmentShader);
		
		// Verificam compilarea
		CGerror error = cgGetError();
		if (error != CG_NO_ERROR)
		{
			// Logam eroarea si iesim
			DEBUG_ERR("Cg fragment shader compilation failed: %s\n", cgGetErrorString(error));

			const char *strError = cgGetLastListing(m_cgContext);
			if( strError )
			{
				DEBUG_ERR("%s\n", strError );
			}

			return false;
		}
		else
		{
			DEBUG_OUT("Cg fragment shader compilation successful\n");
		}
	}

	return true; // Succes
}

//-----------------------------------------------------------------------------

bool CgProgram::Link()
{
	// Combinam vertex si fragment shader-ul intr-un singur shader(program);
	// numai daca exista si vertex si fragment shader
	if (m_cgVertexShader && m_cgFragmentShader)
	{
		CGprogram shaders[] = { m_cgVertexShader, m_cgFragmentShader };
		m_cgCombinedShader = cgCombinePrograms(2, shaders);

		CGerror error = cgGetError();
		if (error != CG_NO_ERROR)
		{
			DEBUG_ERR("Cg program link error: %s\n", cgGetErrorString(error));

			const char *strError = cgGetLastListing(m_cgContext);
			if( strError )
			{
				DEBUG_ERR("%s\n", strError );
			}

			return false;
		}

		// Distrugem shader-ele de care nu mai avem nevoie
		cgDestroyProgram(m_cgVertexShader);
		cgDestroyProgram(m_cgFragmentShader);
	}
	else if (m_cgVertexShader)
	{
		m_cgCombinedShader = m_cgVertexShader;
	}
	else if (m_cgFragmentShader)
	{
		m_cgCombinedShader = m_cgFragmentShader;
	}

	if( m_cgCombinedShader )
	{
		// Incarcam shader-ul, pregatind-ul pt. binding
		cgGLLoadProgram(m_cgCombinedShader);
	}
	else
	{
		DEBUG_OUT("Cg shaders linking unsuccessful\n");
		return false;
	}
	
	return true;
}

//-----------------------------------------------------------------------------

void CgProgram::Use(bool use)
{
	if (!m_cgCombinedShader)
	{
		return;
	}
	
	if (use && !m_inUse)
	{
		m_inUse = true;

		// Activam profilurile necesare
		if (m_cgVertexProfile != CG_PROFILE_UNKNOWN)
		{
			cgGLEnableProfile(m_cgVertexProfile);
		}
		if (m_cgFragmentProfile != CG_PROFILE_UNKNOWN)
		{
			cgGLEnableProfile(m_cgFragmentProfile);
		}

		// Activarea programului propriu-zisa
		cgGLBindProgram(m_cgCombinedShader);

		processSavedVars();
	}
	else if(!use && m_inUse)
	{
		m_inUse = false;

		// Dezactivam profilurile
		if (m_cgVertexProfile != CG_PROFILE_UNKNOWN)
		{
			cgGLDisableProfile(m_cgVertexProfile);
		}
		if (m_cgFragmentProfile != CG_PROFILE_UNKNOWN)
		{
			cgGLDisableProfile(m_cgFragmentProfile);
		}
		
		// Dezactivam programul
		cgGLUnbindProgram(m_cgVertexProfile);
		cgGLUnbindProgram(m_cgFragmentProfile);
	}
}

//-----------------------------------------------------------------------------

bool CgProgram::Validate()
{
	// API-ul de Cg pt. OpenGL nu are metode validare a programului(cum exista pt. GLSL)
	// asa ca facem o validare simpla
	//DEBUG_WAR("CgProgram::Validate() calling this method is useless\n");
	return m_cgCombinedShader != NULL;
}

//-----------------------------------------------------------------------------

void CgProgram::LoadParameters()
{
	if (m_cgCombinedShader == NULL)
	{
		return; // Nu avem niciun program incarcat
	}

	CGenum namespaces[] = { CG_GLOBAL, CG_PROGRAM };
	for (int namespaceIdx = 0; namespaceIdx < 2; namespaceIdx++)
	{
		// Incarcam toti parametrii din namespace
		CGparameter param = cgGetFirstParameter(m_cgCombinedShader, namespaces[namespaceIdx]);
		while (param)
		{
			// Numele parametrului
			std::string name = std::string(cgGetParameterName(param));
			
			// Il punem in map
			m_cgParametersMap[name] = param;
			
			// Urmatorul parametru
			param = cgGetNextParameter(param);
		}
	}
}

//-----------------------------------------------------------------------------

bool CgProgram::HasParameter(const char* paramName)
{
	// Folosim pointerii la parametri deja stocati
	std::string parameterName(paramName);
	if (m_cgParametersMap.find(parameterName) != m_cgParametersMap.end())
	{
		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------

CGparameter CgProgram::GetParameter(const char* paramName)
{
	// Folosim pointerii la parametri deja stocati
	std::string parameterName(paramName);
	if (m_cgParametersMap.find(parameterName) != m_cgParametersMap.end())
	{
		return m_cgParametersMap[parameterName];
	}

	CGparameter param = cgGetNamedParameter(m_cgCombinedShader, paramName);
	std::string name;
	if( !param )
	{
		DEBUG_ERR( "[CGProgram] Could not find parameter '%s' for shader '%s'!\n", paramName, GetFilename().c_str() );
		name = paramName;
	}
	else
	{
		name = std::string(cgGetParameterName(param));
	}
	m_cgParametersMap[name] = param;

	return param;
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter1i(const char* paramName, int value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter1i(param, value);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(0,param,value));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter2i(const char* paramName, int* value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter2i(param, value[0], value[1]);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(1,param,value,2));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter3i(const char* paramName, int* value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter3i(param, value[0], value[1], value[2]);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(2,param,value,3));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter4i(const char* paramName, int* value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter4i(param, value[0], value[1], value[2], value[3]);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(3,param,value,4));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter1f(const char* paramName, float value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter1f(param, value);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(4,param,value));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter2f(const char* paramName, float* value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter2f(param, value[0], value[1]);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(5,param,value,2));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter3f(const char* paramName, float* value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter3f(param, value[0], value[1], value[2]);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(6,param,value,3));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameter4f(const char* paramName, float* value)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		if( m_inUse )
		{
			cgSetParameter4f(param, value[0], value[1], value[2], value[3]);
		}
		else
		{
			m_cgShaderVarSaves.push_back(_shader_var_cg_(7,param,value,4));
		}
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameterTexture(const char* paramName, GLuint textureHandle, GLuint textureUnit)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		cgGLSetTextureParameter(param, textureHandle);
	}
}

//-----------------------------------------------------------------------------

void CgProgram::EnableParameterTexture(const char* paramName)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		cgGLEnableTextureParameter(param);
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameterStateMatrix(const char* paramName, CGGLenum stateMatrixType, CGGLenum transform)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		cgGLSetStateMatrixParameter(param, stateMatrixType , transform);
	}
}

//-----------------------------------------------------------------------------

void CgProgram::SetParameterMatrixf(const char* paramName, GLfloat* mat)
{
	CGparameter param = this->GetParameter(paramName);
	if (param)
	{
		cgGLSetMatrixParameterfc( param, mat );
	}
}

//-----------------------------------------------------------------------------

void CgProgram::Delete()
{
	this->Use(false);
	if (m_cgCombinedShader)
	{
		cgDestroyProgram(m_cgCombinedShader);
	}
	if (m_cgContext)
	{
		cgDestroyContext(m_cgContext);
	}
}

//-----------------------------------------------------------------------------

CgProgram::~CgProgram(void)
{
	this->Delete();	
}

//-----------------------------------------------------------------------------

void	CgProgram::processSavedVars()
{
	while(m_cgShaderVarSaves.size())
	{
		_shader_var_cg_ sh = m_cgShaderVarSaves.back();
		m_cgShaderVarSaves.pop_back();

		switch(sh.func)
		{
		case 0:
			{
				cgSetParameter1i( sh.param, sh.value.vInt[0] );
			}
			break;

		case 1:
			{
				cgSetParameter2i( sh.param, sh.value.vInt[0], sh.value.vInt[1] );
			}
			break;

		case 2:
			{
				cgSetParameter3i( sh.param, sh.value.vInt[0], sh.value.vInt[1], sh.value.vInt[2] );
			}
			break;

		case 3:
			{
				cgSetParameter4i( sh.param, sh.value.vInt[0], sh.value.vInt[1], sh.value.vInt[2], sh.value.vInt[3] );
			}
			break;

		case 4:
			{
				cgSetParameter1f( sh.param, sh.value.vFloat[0] );
			}
			break;

		case 5:
			{
				cgSetParameter2f( sh.param, sh.value.vFloat[0], sh.value.vFloat[1] );
			}
			break;

		case 6:
			{
				cgSetParameter3f( sh.param, sh.value.vFloat[0], sh.value.vFloat[1], sh.value.vFloat[2] );
			}
			break;

		case 7:
			{
				cgSetParameter4f( sh.param, sh.value.vFloat[0], sh.value.vFloat[1], sh.value.vFloat[2], sh.value.vFloat[3] );
			}
			break;

		default:
			ASSERT( false && "That function in not implemnted!" );
		}
	}
}

//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
